//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//

#include <windows.h>
#include <ceddk.h>
#include <nled.h>
#include <led_drvr.h>
#include <monahans.h>
#include <types.h>
#include <nled_pdd.h>
#include <xllp_defs.h>
#include <xllp_gpio.h>
#include <xllp_gpio_plat.h>
#include <xllp_mfp.h>
#include <xllp_mfp_plat.h>
#include <Littleton_registry.h>

#include "GPX_XLLP.h"

extern LPCWSTR gpszInitContext;
#define MFP_SIZE 0x700

//------------------------------------------------------------------------------
// NLED Driver Context

typedef struct {
    INT OnOffBlinkState;
    volatile LONG OnTimeMs;
    volatile LONG OffTimeMs;
} LED_STATE;

typedef struct {
    NLED_SUPPORTS_INFO Support[NOTIFICATION_LED_COUNT];
    LED_STATE State[NOTIFICATION_LED_COUNT];

    DWORD MinBlinkResolutionMs;
    DWORD ThreadPriority;

    DWORD BlinkingLEDCount;

    volatile BOOL ThreadExiting;
    HANDLE hLedBlinkThread;
    HANDLE hBlinkThreadEvent;

    XLLP_VUINT32_T* g_pMFPRegs;
    XLLP_GPIO_T* g_pGPIORegs;

    CRITICAL_SECTION csLeds;
} NLED_DEVICE;

//------------------------------------------------------------------------------
// Global structure

static NLED_DEVICE s_Device = 
{
    {
        { GREEN_LED, 62500, FALSE, TRUE, TRUE, FALSE, FALSE }
    },

    {
        { NLED_STATE_OFF }
    },

    50,
    152
};

//------------------------------------------------------------------------------
// Helper Functions

static inline BOOL 
IsValidLED( UINT LedNum )
{
    return LedNum < NOTIFICATION_LED_COUNT;
}

static inline BOOL
IsValidState( INT OffOnBlink )
{
    return (
        (OffOnBlink == NLED_STATE_OFF) || 
        (OffOnBlink == NLED_STATE_ON) || 
        (OffOnBlink == NLED_STATE_BLINKING)
        );
}

//------------------------------------------------------------------------------
//
// Function: NLedDriverInitialize
//
// The NLED MDD calls this routine to initialize the underlying NLED hardware.
// This routine should return TRUE if successful.  If there's a problem
// it should return FALSE and call SetLastError() to pass back the reason
// for the failure.

BOOL WINAPI
NLedDriverInitialize(
                    VOID
                    )
{
    PHYSICAL_ADDRESS PA;    
    BOOL rc = FALSE;
    DWORD dwRegValue, dwRegReturn, dwType, dwSize = sizeof(dwRegValue);
    HKEY hKey;

    DEBUGMSG( ZONE_FUNCTION, ( 
        L"INFO: NLedDriverInitialize: "
        L" Invoked\r\n" 
        ));

    // Map register space
    if( !s_Device.g_pMFPRegs ) 
    {
        PA.QuadPart = MONAHANS_BASE_REG_PA_MFP;
	 s_Device.g_pMFPRegs = (XLLP_VUINT32_T*) MmMapIoSpace(PA, MFP_SIZE, FALSE); 
    }

    if( !s_Device.g_pGPIORegs ) 
    {
        PA.QuadPart = MONAHANS_BASE_REG_PA_GPIO;
        s_Device.g_pGPIORegs = (XLLP_GPIO_T*) MmMapIoSpace(PA, sizeof(XLLP_GPIO_T), FALSE);
    }
#if 0    
    /* Call_GPX */  
    GPX_SetDirection(XLLP_GPIO_NLED, XLLP_GPIO_DIRECTION_OUT);
#endif

    // Read device parameters
    dwRegReturn = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NLED_REGKEY, 0, KEY_ALL_ACCESS, &hKey);
    if( ERROR_SUCCESS == dwRegReturn )
    {
        // Minimum Blink Resolution
        dwRegReturn = RegQueryValueEx(hKey, TEXT("ThreadBlinkResolutionMs"), NULL, &dwType, (LPBYTE) &dwRegValue, (LPDWORD)&dwSize);
        if (ERROR_SUCCESS == dwRegReturn && REG_DWORD == dwType )
        {
            s_Device.MinBlinkResolutionMs = dwRegValue;
        }

        // Blink Thread Priority
        dwRegReturn = RegQueryValueEx(hKey, TEXT("ThreadBlinkResolutionMs"), NULL, &dwType, (LPBYTE) &dwRegValue, (LPDWORD)&dwSize);
        if (ERROR_SUCCESS == dwRegReturn && REG_DWORD == dwType )
        {
            s_Device.ThreadPriority= dwRegValue;
        }
    }

    // Create events
    s_Device.hBlinkThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (s_Device.hBlinkThreadEvent == NULL)
    {
        DEBUGMSG(ZONE_ERROR, (
            L"ERROR: NLedDriverInitialize: "
            L"Could not create hBlinkThreadEvent event.\r\n"
            ));
        rc = FALSE;
        goto cleanUp;
    }

    // Initialize critical sections
    InitializeCriticalSection(&s_Device.csLeds);

    // Start with NO blinking leds
    s_Device.BlinkingLEDCount = 0;
    s_Device.hLedBlinkThread = NULL;

    rc = TRUE;

cleanUp:
    return rc;
}


//------------------------------------------------------------------------------
//
// Function: NLedDriverDeInitialize
//
// The NLED MDD calls this routine to deinitialize the underlying NLED
// hardware as the NLED driver is unloaded.  It should return TRUE if 
// successful.  If there's a problem this routine should return FALSE 
// and call SetLastError() to pass back the reason for the failure.

BOOL WINAPI
NLedDriverDeInitialize(
                    VOID
                    )
{

    DEBUGMSG( ZONE_FUNCTION, ( 
        L"INFO: NLedDriverDeInitialize: "
        L" Invoked\r\n" 
        ));

    // Close Blink Thread
    if( s_Device.hLedBlinkThread )
    {
        s_Device.ThreadExiting = TRUE;
        SetEvent(s_Device.hBlinkThreadEvent);
        WaitForSingleObject(
            s_Device.hLedBlinkThread, 
            INFINITE
            );

        CloseHandle(s_Device.hLedBlinkThread);
        CloseHandle(s_Device.hBlinkThreadEvent);
        s_Device.hLedBlinkThread = NULL;
        s_Device.hBlinkThreadEvent = NULL;
    }

    // Turn OFF LEDs
    /* Call_GPX */
    GPX_SetOutputLevel(XLLP_GPIO_NLED, XLLP_LO);

    // Delete critical sections
    DeleteCriticalSection(&s_Device.csLeds);

    // Unmap registers
    if( s_Device.g_pGPIORegs ) 
    {
        MmUnmapIoSpace((PVOID)s_Device.g_pGPIORegs, sizeof(XLLP_GPIO_T));
        s_Device.g_pGPIORegs = NULL;
    }
    if( s_Device.g_pMFPRegs ) 
    {
        MmUnmapIoSpace((PVOID)s_Device.g_pMFPRegs, MFP_SIZE); 
        s_Device.g_pMFPRegs = NULL;
    }

    return (TRUE);
}


//------------------------------------------------------------------------------
//
//  Function:  NLedDriverBlinkThread
//
//  This IST will be spawned to support software driven LED blinking
//
//
DWORD
NLedDriverBlinkThread(
    VOID *pContext
    )
{
    NLED_DEVICE *pDevice = (NLED_DEVICE*)pContext;

    if( !pContext )
    {
        DEBUGMSG(ZONE_ERROR, (
            L"ERROR: NLedDriverBlinkThread: "
            L"Invalid context passed to thread.\r\n"
            ));

        return ERROR_INVALID_DATA;
    }

    DEBUGMSG(ZONE_FUNCTION, (
        L"INFO: NLedDriverBlinkThread: "
        L"NLED Blinking thread started.\r\n"
        ));

    // With only one LED, we want to turn it off, then turn it on.
    ASSERT( s_Device.State[GREEN_LED].OnOffBlinkState == NLED_STATE_BLINKING );

    // Loop until we are not stopped...
    while ( !pDevice->ThreadExiting )
    {
        /* Call_GPX */
        GPX_SetOutputLevel(XLLP_GPIO_NLED, XLLP_HI);

        WaitForSingleObject( pDevice->hBlinkThreadEvent, s_Device.State[GREEN_LED].OnTimeMs);
        if( pDevice->ThreadExiting) break;

        // And off...
        GPX_SetOutputLevel(XLLP_GPIO_NLED, XLLP_LO);

        WaitForSingleObject( pDevice->hBlinkThreadEvent, s_Device.State[GREEN_LED].OffTimeMs);        
    }

    DEBUGMSG(ZONE_FUNCTION, (
        L"INFO: NLedDriverBlinkThread: "
        L"Thread exiting.\r\n"
        ));

    return ERROR_SUCCESS;
}

//------------------------------------------------------------------------------
// This routine retrieves information about the NLED device(s) that
// this driver supports.  The nInfoId parameter indicates what specific
// information is being queried and pOutput is a buffer to be filled in.
// The size of pOutput depends on the type of data being requested.  This
// routine returns TRUE if successful, or FALSE if there's a problem -- in
// which case it also calls SetLastError() to pass back more complete
// error information.  The NLED MDD invokes this routine when an application
// calls NLedGetDeviceInfo().
BOOL
WINAPI
NLedDriverGetDeviceInfo(
                       INT     nInfoId,
                       PVOID   pOutput
                       )
{
    BOOL fOk = TRUE;

    DEBUGMSG( ZONE_FUNCTION, ( 
        L"NLedDriverGetDeviceInfo: "
        L"Invoked\r\n" 
        ));

    // All possible nInfoId values require an out buffer
    if( !pOutput )
    {
        fOk = FALSE;
        SetLastError( ERROR_INVALID_PARAMETER );
        goto cleanUp;
    }

    if ( nInfoId == NLED_COUNT_INFO_ID ) 
    {
        struct NLED_COUNT_INFO  *pLEDOutInfo = (struct NLED_COUNT_INFO*)pOutput;

        __try {
            pLEDOutInfo -> cLeds = NOTIFICATION_LED_COUNT;
        } 
        __except(EXCEPTION_EXECUTE_HANDLER) {
            fOk = FALSE;
            SetLastError(ERROR_INVALID_PARAMETER);
        }
    }
    else if( nInfoId == NLED_SUPPORTS_INFO_ID )
    {
        struct NLED_SUPPORTS_INFO *pLEDSupportsOut = ( struct NLED_SUPPORTS_INFO* )pOutput;

        __try 
        {
            if ( IsValidLED( pLEDSupportsOut->LedNum ))
            {
                NLED_SUPPORTS_INFO *pLEDSupport = &s_Device.Support[pLEDSupportsOut->LedNum];
                
                pLEDSupportsOut->lCycleAdjust = pLEDSupport->lCycleAdjust;
                pLEDSupportsOut->fAdjustTotalCycleTime = pLEDSupport->fAdjustTotalCycleTime;
                pLEDSupportsOut->fAdjustOnTime = pLEDSupport->fAdjustOnTime;
                pLEDSupportsOut->fAdjustOffTime = pLEDSupport->fAdjustOffTime;
                pLEDSupportsOut->fMetaCycleOn = pLEDSupport->fMetaCycleOn;
                pLEDSupportsOut->fMetaCycleOff = pLEDSupport->fMetaCycleOff;
            }
            else
            {
                fOk = FALSE;
                SetLastError( ERROR_INVALID_PARAMETER );
            }
        }
        __except( EXCEPTION_EXECUTE_HANDLER ) 
        {
            fOk = FALSE;
            SetLastError( ERROR_INVALID_PARAMETER );
        }
    }
    else if( nInfoId == NLED_SETTINGS_INFO_ID )
    {
        struct NLED_SETTINGS_INFO *pLEDSettingsOut = ( struct NLED_SETTINGS_INFO* )pOutput;

         __try 
        {
            if ( IsValidLED( pLEDSettingsOut->LedNum ))
            {
                LED_STATE *pState = &s_Device.State[ pLEDSettingsOut->LedNum ];
                
                pLEDSettingsOut->OffOnBlink = pState->OnOffBlinkState; 
                pLEDSettingsOut->OnTime = pState->OnTimeMs * 1000;
                pLEDSettingsOut->OffTime = pState->OffTimeMs * 1000;
                pLEDSettingsOut->TotalCycleTime = pLEDSettingsOut->OnTime + pLEDSettingsOut->OffTime;
                pLEDSettingsOut->MetaCycleOn = 0;
                pLEDSettingsOut->MetaCycleOff = 0;
            }
            else
            {
                fOk = FALSE;
                SetLastError( ERROR_INVALID_PARAMETER );
            }
        }
        __except( EXCEPTION_EXECUTE_HANDLER ) 
        {
            fOk = FALSE;
            SetLastError( ERROR_INVALID_PARAMETER );
        }
    }
    else // invalid nInfoId
    {
        fOk = FALSE;
        SetLastError(ERROR_INVALID_PARAMETER);
    }

cleanUp:
    DEBUGMSG(ZONE_FUNCTION || (!fOk && ZONE_WARN), (
        L"NLedDriverGetDeviceInfo: "
        L"Returning %d\r\n", fOk 
        ));

    return (fOk);
}


//------------------------------------------------------------------------------
//
// Function: NLedDriverSetDevice
//
// This routine changes the configuration of an LED.  The nInfoId parameter
// indicates what kind of configuration information is being changed.  
// Currently only the NLED_SETTINGS_INFO_ID value is supported.  The pInput
// parameter points to a buffer containing the data to be updated.  The size
// of the buffer depends on the value of nInfoId.  This routine returns TRUE
// if successful or FALSE if there's a problem -- in which case it also calls
// SetLastError().  The NLED MDD invokes this routine when an application 
// calls NLedSetDevice().
BOOL
WINAPI
NLedDriverSetDevice(
                   INT     nInfoId,
                   PVOID   pInput
                   )
{
    BOOL fOk = TRUE;
    struct NLED_SETTINGS_INFO *p;

    DEBUGMSG( ZONE_FUNCTION, ( 
        L"NLedDriverSetDevice: invoked\r\n" 
        ));

    // For the only case of nInfoId, we require a valid memory pointer in pInput
    if( (!pInput) || (nInfoId != NLED_SETTINGS_INFO_ID) )
    {
        fOk = FALSE;
        SetLastError( ERROR_INVALID_PARAMETER );
        goto cleanUp;
    }

    __try
    {
        p = ( struct NLED_SETTINGS_INFO* )pInput;

        if( !IsValidLED( p->LedNum ) || !IsValidState( p->OffOnBlink )) 
        {
            fOk = FALSE;
            SetLastError( ERROR_INVALID_PARAMETER);
            goto cleanUp;
        }

        // Do work on new structure, ignore MetaCycleOff/MetaCycleOn, TotalCycleTime since it is unsupported
        // This means we should update On/Off/Blink, and if Blink, the On/Off times.
        DWORD oldOnOffBlinkState = s_Device.State[p->LedNum].OnOffBlinkState;
        DWORD newOnOffBlinkState = p->OffOnBlink;
        BOOL ChangingLEDStates = (oldOnOffBlinkState != newOnOffBlinkState);

        // Set LED to its new state
        s_Device.State[p->LedNum].OnOffBlinkState = newOnOffBlinkState;

        // If now blinking, update the On/Off times
        if(newOnOffBlinkState == NLED_STATE_BLINKING )
        {
            DWORD newBlinkOnTimeMs = ( p->OnTime + 500 ) / 1000;
            DWORD newBlinkOffTimeMs = ( p->OffTime + 500 ) / 1000;

            // Sanity check the parameters passed
            if( (p->OnTime < 0) || (p->OffTime < 0) ) 
            {
                DEBUGMSG( ZONE_ERROR , ( 
                    L"NLedDriverSetDevice: "
                    L"Blink On and Off times cannot be negative.\r\n"
                    ));
                fOk = FALSE;
                SetLastError( ERROR_INVALID_PARAMETER );
                goto cleanUp;
            }

            // Low clip the blinking times
            if( newBlinkOnTimeMs < s_Device.MinBlinkResolutionMs)
            {
                DEBUGMSG( ZONE_WARN , ( 
                    L"NLedDriverSetDevice: "
                    L"New blink ON time (%dms) set below min, clipping to %dms\r\n", 
                    newBlinkOnTimeMs , s_Device.MinBlinkResolutionMs
                    ));
    
                newBlinkOnTimeMs = s_Device.MinBlinkResolutionMs;
            }
            if( newBlinkOffTimeMs < s_Device.MinBlinkResolutionMs)
            {
                DEBUGMSG( ZONE_WARN , ( 
                    L"NLedDriverSetDevice: "
                    L"New blink OFF time (%dms) set below min, clipping to %dms\r\n", 
                    newBlinkOffTimeMs , s_Device.MinBlinkResolutionMs
                    ));
    
                newBlinkOffTimeMs = s_Device.MinBlinkResolutionMs;
            }

            s_Device.State[p->LedNum].OffTimeMs = newBlinkOffTimeMs;
            s_Device.State[p->LedNum].OnTimeMs = newBlinkOnTimeMs;
        }

        if( ChangingLEDStates )
        {
            if( oldOnOffBlinkState == NLED_STATE_BLINKING ) //was blinking, not anymore
            {
                s_Device.BlinkingLEDCount--;

                if( 0 == s_Device.BlinkingLEDCount )
                {
                    // End blink thread when no more blinking LEDs
                    ASSERT(s_Device.hLedBlinkThread);

                    if(s_Device.hLedBlinkThread)
                    {
                        s_Device.ThreadExiting = TRUE;
                        SetEvent(s_Device.hBlinkThreadEvent);
                        WaitForSingleObject(s_Device.hLedBlinkThread, INFINITE);
                        CloseHandle(s_Device.hLedBlinkThread);
                        s_Device.hLedBlinkThread = NULL;
                    }
                }
            }
            else if( newOnOffBlinkState == NLED_STATE_BLINKING) //not blinking, now blinking
            {
                s_Device.BlinkingLEDCount++;

                if( 1 == s_Device.BlinkingLEDCount )
                {
                    // First blinking LED should start the blink thread
                    ASSERT( NULL == s_Device.hLedBlinkThread );
                    s_Device.ThreadExiting = FALSE;
                    s_Device.hLedBlinkThread = CreateThread(
                        NULL, 0, NLedDriverBlinkThread, &s_Device, 0, NULL
                        );

                    if( NULL == s_Device.hLedBlinkThread )
                    {
                        DEBUGMSG(ZONE_ERROR, (
                            L"ERROR: NLedDriverSetDevice: "
                            L"Error starting LED blink thread.\r\n"
                            ));
                       
                        goto cleanUp;           
                    }

                    // Set thread priority
                    CeSetThreadPriority(s_Device.hLedBlinkThread, s_Device.ThreadPriority);
                }
            }
        }

        // If new state is just "on" or "off", force our one LED to this state
        // Note that if the new state is blinking, the blink thread we just spawned will take care of everything
        if( newOnOffBlinkState == 0 ) 
        {
            /* Call_GPX */
            GPX_SetOutputLevel(XLLP_GPIO_NLED, XLLP_HI);
        }
        else if (newOnOffBlinkState == 1)
        {
            /* Call_GPX */
            GPX_SetOutputLevel(XLLP_GPIO_NLED, XLLP_LO);
        }

    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        fOk = FALSE;
        SetLastError( ERROR_INVALID_PARAMETER );
        goto cleanUp;
    }

cleanUp:
    DEBUGMSG( ZONE_FUNCTION || ( !fOk && ZONE_WARN ), ( 
        L"NLedDriverSetDevice: "
        L"Returning %d\r\n", fOk 
        ));
    return( fOk );
}


// This routine is invoked by the driver MDD when the system suspends or
// resumes.  The power_down flag indicates whether the system is powering 
// up or powering down.
VOID WINAPI
NLedDriverPowerDown(
                   BOOL power_down
                   )
{
    // Turn OFF LED to save power in suspend
    if( power_down && s_Device.g_pGPIORegs )
        /* Call_GPX */
        GPX_SetOutputLevel(XLLP_GPIO_NLED, XLLP_LO);

    return;
}

